package org.OpenGeoPortal.Test;

import java.util.List;


import org.junit.* ;
import static org.junit.Assert.* ;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



/**
 * exercise the GIS portal web application using the Selenium 2 API 
 * @author smcdon08
 *
 * This code retrieves elements from the DOM by their id, name and row count in a table.
 * Should there be separate test-only names also associated with DOM elements so they
 *   are clearly marked as being coupled to testing code? 
 *   
 * Should we break up all the tests so they aren't all in one package?  How should they be divided?
 *   
 * I need to replace the println with real logging.
 */

public class WebAppTest 
{
	
	public enum BrowserType {Firefox, Chrome}
	
	public String url = "http://localhost:8080/OpenGeoPortal/openGeoPortalHome.jsp";
	//public String url = "http://geoportal-dev.atech.tufts.edu/openGeoPortalHome.jsp";
	//public String url = "http://geoportal-demo.atech.tufts.edu";

	public static Logger logger = LoggerFactory.getLogger(WebAppTest.class);;
	
	public WebAppTest()
	{
		
	}
	
	/**
	 * run all the tests on all the browser types
	 */
	@Test
	public void runTests()
	{
		for (BrowserType currentBrowserType : BrowserType.values())
		{
			runTests(currentBrowserType);
		}
	}
	
	
	/**
	 * run tests to verify the web app is working
	 */
	public void runTests(BrowserType browserType)
	{
		logger.info("running tests on {}", browserType);
		WebDriver webDriver = createWebDriver(browserType);
		webDriver.get(url);
		searchResultsExistTest(webDriver);
		sortByTest(webDriver, "colTitle", "Re-sort by name failed");
		sortByTest(webDriver, "colOriginator", "Re-sort by originator failed");
		saveLayerTest(webDriver);
		dateSearchTest(webDriver);
		zoomToWorldTest(webDriver);
		dataTypeTest(webDriver);
		dataSourceTest(webDriver);
		geoCodingTest(webDriver);
		geoCodingAddressTest(webDriver);
	}
	
	/**
	 * note: testing didn't work with the HtmlUnitDriver
	 * @return
	 */
	public static WebDriver createWebDriver(BrowserType browserType)
	{
		WebDriver webDriver = null;
		if (browserType == BrowserType.Firefox)
			webDriver =  new FirefoxDriver();
		else if (browserType == BrowserType.Chrome)
			webDriver =  new ChromeDriver(); 
		return webDriver;
	}
	
	/**
	 * see if any search results are displayed
	 * actually, verify the displayed number of search results is not 0
	 * @param webDriver
	 */
	public void searchResultsExistTest(WebDriver webDriver)
	{
		logger.info("  in searchResultsExistTest");
		int initialLayerCount = getSearchResultsCount(webDriver);
		if (initialLayerCount != 0)
			fail("search should not run on page load.");
		zoomToWorld(webDriver);
		initialLayerCount = getSearchResultsCount(webDriver);
		if (initialLayerCount == 0)
			fail("no search results found.");
		
	}
	
	/**
	 * return the number of results from the search results count label
	 * @param webDriver
	 * @return
	 */
	public static int getSearchResultsCount(WebDriver webDriver)
	{
		WebElement spanElement = webDriver.findElement(By.id("resultsNumber"));
		String text = spanElement.getText(); 
		String temp = text.replace("(", "");
		temp = temp.replace(")", "");
		int layerCount = Integer.parseInt(temp);
		return layerCount;
	}
	
	
	/**
	 * click on the sort button specified by the passed buttonClass and verify the first result changes
	 * note that changing the sort order isn't theoretically guaranteed to change the first item
	 *   in the list, but practically speaking it is
	 * @param webDriver
	 * @param buttonClass
	 * @param errorMessage
	 */
	private static void sortByTest(WebDriver webDriver, String buttonClass, String errorMessage)
	{
		logger.info("  in sortByTest with " + buttonClass);
		String firstTitle = getFirstLayerName(webDriver);
		clickSortByButton(webDriver, buttonClass);
		pause();
		String resortedFirstTitle = getFirstLayerName(webDriver);
		boolean stringsSame = firstTitle.equals(resortedFirstTitle);
		assertFalse(errorMessage, stringsSame);
	}
	
	
	/**
	 * sleep for a brief period so the page has time to update
	 * this is important when the map is changed and both the map and search results
	 *   must be updated
	 */
	private static void pause()
	{
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * returns the WebElement for the first layer in the search results table
	 * @param webDriver
	 * @return
	 */
	private static WebElement getFirstResult(WebDriver webDriver)
	{
		WebElement searchResults = webDriver.findElement(By.id("searchResults"));
		List<WebElement> layerRows = searchResults.findElements(By.tagName("tr"));
		if (layerRows.size() > 1)
		{
			WebElement firstRow = layerRows.get(1);
			return firstRow;
		}
		fail("did not find first result");
		return null;
	}
	
	
	/**
	 * returns the name of the first layer in the search results table
	 * @param webDriver
	 * @return
	 */
	private static String getFirstLayerName(WebDriver webDriver)
	{
		WebElement firstRow = getFirstResult(webDriver);
		WebElement title = firstRow.findElement(By.className("colTitle"));
		String title$ = title.getText();
		return title$;
	}
	
	/**
	 * save the first layer and verifies the saved layer count increments by one
	 * this test assumes the first layer is not saved
	 * @param webDriver
	 */
	private static void saveLayerTest(WebDriver webDriver)
	{
		logger.info("  in saveLayerTest");
		int startLayerCount = getSavedLayersCount(webDriver);
		saveFirstLayer(webDriver);
		pause();
		int endLayerCount = getSavedLayersCount(webDriver);
		assertEquals("Save layer test failed", startLayerCount + 1, endLayerCount);
	}
	
	/**
	 * click the save button on the first search results
	 * this will have to change if the columns get re-arranged
	 * @param webDriver
	 */
	private static void saveFirstLayer(WebDriver webDriver)
	{
		WebElement firstRow = getFirstResult(webDriver);
		List<WebElement> buttonElements = firstRow.findElements(By.className("saveControl"));
		WebElement saveButton = buttonElements.get(0);
		saveButton.click();
	}
	
	/**
	 * return the saved layers count from the title on the saved layers tab
	 * @param webDriver
	 * @return
	 */
	private static int getSavedLayersCount(WebDriver webDriver)
	{
		WebElement element = webDriver.findElement(By.id("savedLayersNumber"));
		String text = element.getText();
		text = text.replace("(", "");
		text = text.replace(")", ""); 
		int count = Integer.parseInt(text);
		return count;
	}
	
	/**
	 * click the passed sort button on the search results table column name row
	 * note that we get the actual element from the first row of the table by its class name
	 * @param webDriver
	 * @param buttonClass
	 */
	private static void clickSortByButton(WebDriver webDriver, String buttonClass)
	{
		WebElement searchResults = webDriver.findElement(By.id("searchResults"));		
		List<WebElement> layerRows = searchResults.findElements(By.tagName("tr"));	
		WebElement titlesRow = layerRows.get(0);	
		WebElement nameColumn = titlesRow.findElement(By.className(buttonClass));
		if (nameColumn != null)
			nameColumn.click();
		else
			fail("Did not find button to sort results by name");
	}
	
	/**
	 * enters text into the geocoding search box and verifies the number of search results decreases
	 * initially, it zooms the map all the way out
	 * @param webDriver
	 */
	private static void geoCodingTest(WebDriver webDriver)
	{
		logger.info("  in geoCodingTest");
		zoomToWorld(webDriver);
		pause();
		int startResultsCount = getSearchResultsCount(webDriver);
		setLocation(webDriver, "Boston, MA");
		pause();
		int endResultsCount = getSearchResultsCount(webDriver);
		assertTrue("geoCoding test failed", startResultsCount > endResultsCount);
		
	}
	
	/**
	 * geocoding to an address returns a slightly different kind of object, must test
	 * @param webDriver
	 */
	private static void geoCodingAddressTest(WebDriver webDriver)
	{
		logger.info("  in geoCodingAddressTest");
		zoomToWorld(webDriver);
		pause();
		int startResultsCount = getSearchResultsCount(webDriver);
		setLocation(webDriver, "1600 Pennsylvania ave, washington dc");
		pause();
		int endResultsCount = getSearchResultsCount(webDriver);
		assertTrue("geoCoding test failed", startResultsCount > endResultsCount);
		
	}
	/**
	 * click on the zoom to world button
	 * @param webDriver
	 */
	private static void zoomToWorld(WebDriver webDriver)
	{
		clickButtonByClass(webDriver, "olControlZoomToMaxExtentItemInactive");
	}
	
	/**
	 * click on the advanced search link
	 * @param webDriver
	 */
	private static void advancedSearch(WebDriver webDriver)
	{
		WebElement advancedSearch = webDriver.findElement(By.id("moreSearchOptions"));
		advancedSearch.click();
	}
	
	/**
	 * enter the passed text into the html element with the passed element id
	 * @param webDriver
	 * @param fieldId
	 * @param value
	 */
	private static void setField(WebDriver webDriver, String fieldId, String value)
	{
		WebElement field = webDriver.findElement(By.id(fieldId));
		field.clear();
		field.sendKeys(value);
	}
	
	/**
	 * click the button corresponding to the passed element id
	 * @param webDriver
	 * @param buttonId
	 */
	private static void clickButton(WebDriver webDriver, String buttonId)
	{
		WebElement button = webDriver.findElement(By.id(buttonId));
		button.click();
	}
	
	/**
	 * click the button corresponding to the passed class name
	 * @param webDriver
	 * @param className
	 */
	private static void clickButtonByClass(WebDriver webDriver, String className)
	{
		WebElement button = webDriver.findElement(By.className(className));
		button.click();
	}
	
	private static void setLocation(WebDriver webDriver, String location)
	{
		WebElement geoSearch = webDriver.findElement(By.id("geosearch"));
		geoSearch.click();  //clears field
		geoSearch.sendKeys(location);
		clickButton(webDriver, "goButton");
	}
	
	/**
	 * verify number of search results increases when we zoom out
	 * @param webDriver
	 */
	private static void zoomToWorldTest(WebDriver webDriver)
	{
		logger.info("  in zoomToWorldTest");
		setLocation(webDriver, "Boston, MA");
		pause();
		int bostonLayerCount = getSearchResultsCount(webDriver);
		zoomToWorld(webDriver);
		pause();
		int worldLayerCount = getSearchResultsCount(webDriver);
		assertTrue("zoom to world button test failed.", worldLayerCount > bostonLayerCount);
	}
	
	/**
	 * verify that the data types buttons do something
	 * turn all data types off and test them one at a time
	 * @param webDriver
	 */
	private static void dataTypeTest(WebDriver webDriver)
	{
		logger.info("  in dataTypeTest");
		clickButton(webDriver, "moreSearchOptions");
		clickButton(webDriver, "dataTypeCheckRaster");  // clear
		clickButton(webDriver, "dataTypeCheckVector");  // clear
		clickButton(webDriver, "dataTypeCheckMap");  // clear
		zoomToWorld(webDriver);
		pause();
		int initialLayerCount = getSearchResultsCount(webDriver);
		clickButton(webDriver, "dataTypeCheckRaster");
		clickButton(webDriver, "advancedSearchSubmit");
		pause();
		int rasterLayerCount = getSearchResultsCount(webDriver);
		clickButton(webDriver, "dataTypeCheckRaster");  // clear
		clickButton(webDriver, "dataTypeCheckVector");
		clickButton(webDriver, "advancedSearchSubmit");
		pause();
		int vectorLayerCount = getSearchResultsCount(webDriver);
		clickButton(webDriver, "dataTypeCheckVector");  // clear
		clickButton(webDriver, "dataTypeCheckMap");
		clickButton(webDriver, "advancedSearchSubmit");
		pause();
		int mapLayerCount = getSearchResultsCount(webDriver);
				
		clickButton(webDriver, "dataTypeCheckRaster");  // turn back on 
		clickButton(webDriver, "dataTypeCheckVector");  // turn back on 
		clickButton(webDriver, "lessSearchOptions");  // reset to basic
		assertTrue("raster test failed", ((initialLayerCount >= rasterLayerCount) && (rasterLayerCount > 0)));
		assertTrue("vector test failed", ((initialLayerCount >= vectorLayerCount) && (vectorLayerCount > 0)));
		assertTrue("map test failed", ((initialLayerCount >= mapLayerCount) && (mapLayerCount > 0)));
		
		int dataTypeTotal = rasterLayerCount + vectorLayerCount + mapLayerCount;
		assertTrue("data type test failed", initialLayerCount >= dataTypeTotal);
		
	}
	
	/**
	 * turn off all sources then turn them on one at a time
	 * @param webDriver
	 */
	private static void dataSourceTest(WebDriver webDriver)
	{
		logger.info("  in dataSourceTest");
		clickButton(webDriver, "moreSearchOptions");
		clickButton(webDriver, "sourceCheckTufts");  //clear
		clickButton(webDriver, "sourceCheckHarvard");   // clear
		clickButton(webDriver, "sourceCheckMit");   // clear
		clickButton(webDriver, "sourceCheckPrinceton");   // clear
		clickButton(webDriver, "sourceCheckMassGis");   // clear
		zoomToWorld(webDriver);
		
		pause();
		int initialLayerCount = getSearchResultsCount(webDriver);
		clickButton(webDriver, "sourceCheckTufts");
		clickButton(webDriver, "advancedSearchSubmit");
		pause();
		int tuftsLayerCount = getSearchResultsCount(webDriver);
		
		clickButton(webDriver, "sourceCheckTufts");  // clear
		clickButton(webDriver, "sourceCheckHarvard"); 
		clickButton(webDriver, "advancedSearchSubmit");
		pause();
		int harvardLayerCount = getSearchResultsCount(webDriver);
		
		clickButton(webDriver, "sourceCheckHarvard");   // clear
		clickButton(webDriver, "sourceCheckMit");
		clickButton(webDriver, "advancedSearchSubmit");
		pause();
		int mitLayerCount = getSearchResultsCount(webDriver); 
		
		clickButton(webDriver, "sourceCheckMit");   // clear
		clickButton(webDriver, "sourceCheckPrinceton");
		clickButton(webDriver, "advancedSearchSubmit");
		pause();
		int princetonLayerCount = getSearchResultsCount(webDriver);
		
		clickButton(webDriver, "sourceCheckPrinceton");   // clear
		clickButton(webDriver, "sourceCheckMassGis");
		clickButton(webDriver, "advancedSearchSubmit");
		pause();
		int massGisLayerCount = getSearchResultsCount(webDriver);
		
		// turn back on 
		clickButton(webDriver, "sourceCheckTufts");  
		clickButton(webDriver, "sourceCheckHarvard");
		clickButton(webDriver, "sourceCheckMit");
		clickButton(webDriver, "sourceCheckPrinceton");
		clickButton(webDriver, "lessSearchOptions");  // reset
		
		assertTrue("Tufts test failed", ((initialLayerCount >= tuftsLayerCount))); // && (tuftsLayerCount > 0)));
		assertTrue("Harvard test failed", ((initialLayerCount >= harvardLayerCount) && (harvardLayerCount > 0)));
		assertTrue("MIT test failed", ((initialLayerCount >= mitLayerCount) && (harvardLayerCount > 0)));
		assertTrue("Princeton test failed", ((initialLayerCount >= princetonLayerCount) && (princetonLayerCount > 0)));
		assertTrue("MassGIS test failed", ((initialLayerCount >= massGisLayerCount) && (massGisLayerCount > 0)));
		
		int totalLayerCount = tuftsLayerCount + harvardLayerCount + mitLayerCount + princetonLayerCount + massGisLayerCount;
		assertTrue("total layer count by source too large", initialLayerCount >= totalLayerCount);
	}
	
	/**
	 * perform multiple searches with different dates 
	 * @param webDriver
	 */
	private static void dateSearchTest(WebDriver webDriver)
	{
		logger.info("  in dateSearchTest");
		clickButton(webDriver, "moreSearchOptions");
		zoomToWorld(webDriver);
		pause();
		int totalLayerCount = getSearchResultsCount(webDriver);
		setField(webDriver, "advancedDateFromText", "0");
		setField(webDriver, "advancedDateToText", "1000");
		clickButton(webDriver, "advancedSearchSubmit");
		pause();
		int veryOldLayers = getSearchResultsCount(webDriver);
		setField(webDriver, "advancedDateFromText", "1000");      
		setField(webDriver, "advancedDateToText", "2020");
		clickButton(webDriver, "advancedSearchSubmit");
		int newerLayers = getSearchResultsCount(webDriver);
		
		// clean-up
		setField(webDriver, "advancedDateFromText", "");
		setField(webDriver, "advancedDateToText", "");
		clickButton(webDriver, "advancedSearchSubmit");
		clickButton(webDriver, "lessSearchOptions");
		
		// did the searches work?
		assertTrue("date search failed, with number of total layers = " + totalLayerCount
					+ " and number of layers between years 0 and 1000 = " + veryOldLayers 
					+ ".  Total number of layers should have been larger then layers between 0 and 1000",
					veryOldLayers < totalLayerCount); 
		assertTrue("data search failed with number of layers between years 0 and 1000 = " + veryOldLayers
					+ " and number of layers between years 1000 and 2020 = " + newerLayers
					+ ".  The number of layers from more recent window should have been greater " 
					+ "then number of ancient layers.", veryOldLayers < newerLayers);
		
		
	}
	
	
}
